﻿using System;
using System.Text;
using System.Data;
using hedefgrup.reports.renault.database;
using System.Collections.Generic;

namespace hedefgrup.reports.common
{
    public class Tree : IEnumerable<Attribute>
    {
        private static SortedList<string, Tree> _ByUniqueID = new SortedList<string, Tree>();
        
        public static readonly string[] TreeTableFieldList = { "UniqueID", "Path", "Key", "Parent", "Type", "TimeStamp" };

        private string _UniqueID;
        public string UniqueID
        {
            get
            {
                return _UniqueID;
            }
            private set
            {
                _UniqueID = value;
                _ByUniqueID.Add(_UniqueID, this);
            }
        }

        public string Path { get; private set; }
        public string Key { get; private set; }
        public Tree Parent { get; private set; }
        public string Type { get; set; }
        public Nullable<DateTime> TimeStamp { get; private set; }

        public string Text
        {
            get
            {
                return this["Text"];
            }
            set
            {
                this["Text"] = value;
            }
        }

        private bool _AttributesSet;
        public SortedList<string, Attribute> _Attributes = new SortedList<string, Attribute>();

        private bool _ChildrenSet;
        private SortedList<string, Tree> _Children = new SortedList<string, Tree>();

        public static Tree MemoryOnlyGetByUniqueID(string uniqueID)
        {
            return _ByUniqueID.ContainsKey(uniqueID) ? _ByUniqueID[uniqueID] : null;
        }

        private static Tree _Root;

        public static Tree Root
        {
            get
            {
                return _Root == null ? _Root = new Tree() { Key = "/", UniqueID = "34E51907-B8F2-436F-ADCF-F5FD69E61010" } : _Root;
            }
        }

        public bool IsRoot
        {
            get
            {
                return this == Root;
            }
        }

        private Tree()
        {
        }

        private Tree(Tree parent, DataRow dataRow)
        {
            UniqueID = dataRow["UniqueID"].ToString();
            Path = dataRow["Path"].ToString();
            Key = dataRow["Key"].ToString();
            Parent = parent;
            Type = dataRow["Type"].ToString();
        }

        private Tree(Tree parent, string key, string type)
        {
            UniqueID = Guid.NewGuid().ToString().ToUpperInvariant();
            Path = (parent != null ? parent.Path : "") + "/" + key;
            Key = key;
            Parent = parent;
            Type = type;
            Persist();
        }

        public static void Invalidate()
        {
            _Root = null;
            _ByUniqueID.Clear();
        }

        public Tree[] Select(string xPath)
        {
            // child1/child2[@Text='text']/*/child3
            TreeXPathParser parser = new TreeXPathParser(xPath.TrimStart('/'));

            List<Tree> temporaryMatches = new List<Tree>();
            Tree[] collectionToTest = new Tree[] { this };

            foreach (TreeXPathSegment xPathSegment in parser.Segments)
            {
                temporaryMatches.Clear();

                foreach (Tree tree in collectionToTest)
                {
                    temporaryMatches.AddRange(tree.Select(xPathSegment));
                }

                collectionToTest = temporaryMatches.ToArray();
            }

            return collectionToTest;
        }

        private Tree[] Select(TreeXPathSegment xPathSegment)
        {
            List<Tree> matches = new List<Tree>();
            foreach (Tree child in Children)
            {
                if (xPathSegment.Test(child))
                {
                    matches.Add(child);
                }
            }
            return matches.ToArray();
        }

        public Tree CreateChild(string key, string type)
        {
            LoadChildren();

            Tree newChild = new Tree(this, key, type);

            _Children.Add(key, newChild);

            return newChild;
        }

        public int Depth
        {
            get
            {
                return IsRoot ? 0 : Path.Split('/').Length - 1;
            }
        }

        public IList<Tree> Children
        {
            get
            {
                LoadChildren();
                return _Children.Values;
            }
        }

        private void LoadChildren()
        {
            if (!_ChildrenSet)
            {
                DataTable childrenData = db.query("SELECT * FROM Tree WHERE Parent = @p_0", UniqueID);
                foreach (DataRow childRow in childrenData.Rows)
                {
                    Tree child = new Tree(this, childRow);
                    if (_Children.ContainsKey(child.Key))
                    {
                        throw new DataException("There are more than one tree nodes with the same path [" + child.Path + "]. Please correct the situation by deleting these records from Tree table.");
                    }
                    _Children.Add(child.Key, child);
                }
                _ChildrenSet = true;
            }
        }

        private void LoadAttributes()
        {
            if (!_AttributesSet)
            {
                _Attributes = Attribute.GetAttributes(this);
                _AttributesSet = true;
            }
        }

        public Tree GetChild(string key)
        {
            LoadChildren();
            return _Children.ContainsKey(key) ? _Children[key] : null;
        }

        public string this[string key]
        {
            get
            {
                return this[key, "*"];
            }
            set
            {
                this[key, "*"] = value;
            }
        }

        public string this[string key, string culture]
        {
            get
            {
                LoadAttributes();

                string attributeKey = key + "_" + culture;
                if (_Attributes.ContainsKey(attributeKey))
                {
                    return _Attributes[attributeKey].Value;
                }
                return null;
            }
            set
            {
                LoadAttributes();

                string attributeKey = key + "_" + culture;
                if (_Attributes.ContainsKey(attributeKey))
                {
                    if (_Attributes[attributeKey].Value != value)
                    {
                        _Attributes[attributeKey].Value = value;
                        _Attributes[attributeKey].Persist();
                    }
                }
                else
                {
                    Attribute newAttribute = new Attribute(this, key, culture, value);
                    _Attributes.Add(attributeKey, newAttribute);
                    newAttribute.Persist();
                }

            }
        }

        private void PersistAttributes()
        {
            foreach (Attribute attribute in _Attributes.Values)
            {
                attribute.Persist();
            }
        }

        private void Persist()
        {
            string updateStatement = "UPDATE Tree SET [Path] = @p_0, [Key] = @p_1, [Parent] = @p_2, [Type] = @p_3, [TimeStamp] = @p_4 WHERE UniqueId = @p_5";

            if (TimeStamp == null)
            {
                string insertStatement = db.prepare_insert("Tree", TreeTableFieldList);
                db.execute(insertStatement, UniqueID, Path, Key, Parent.UniqueID, Type, TimeStamp = DateTime.Now);
            }
            else
            {
                if (db.execute(updateStatement, Path, Key, Parent.UniqueID, Type, UniqueID) == 0)
                {
                    throw new TreeConcurrencyException(this);
                }
            }

            PersistAttributes();
        }

        public string ToHtml(List<Tree> expandedTrees, Tree selectedTree)
        {
            StringBuilder htmlBuilder = new StringBuilder();
            ToHtml(htmlBuilder, expandedTrees, selectedTree);
            return htmlBuilder.ToString();
        }

        public void ToHtml(StringBuilder htmlBuilder, List<Tree> expandedTrees, Tree selectedTree)
        {
            if (IsRoot)
            {
                htmlBuilder.Append("<div class=\"tree\" ><ul>");
            }

            bool isThisExpanded = expandedTrees.Contains(this);

            htmlBuilder.AppendFormat
            (
                "<li style=\"padding-left:{0}px;{5}\"><a href=\"?toggleexpand={1}\">{2}</a> <a href=\"?viewdetails={1}\">{3} ({4})</a></li>",
                10 + Depth * 15,
                UniqueID,
                isThisExpanded ? "-" : "+",
                Key,
                IsRoot ? "ROOT" : Text,
                selectedTree == this ? "font-weight:bold;border:1px solid black;" : ""
            );

            if (isThisExpanded)
            {
                foreach (Tree child in Children)
                {
                    child.ToHtml(htmlBuilder, expandedTrees, selectedTree);
                }

                htmlBuilder.AppendFormat
                (
                    "<li style=\"padding-left:{0}px;{4}\"><a href=\"?appendTo={1}\">{2} ({3})</a></li>",
                    10 + (Depth + 1) * 15,
                    UniqueID,
                    "[ + ]",
                    "Add Child",
                    ""
                );
            }

            if (IsRoot)
            {
                htmlBuilder.Append("</ul></div>");
            }
        }

        public string Dump()
        {
            return Dump(true);
        }

        public string Dump(bool recursive)
        {
            StringBuilder dumpBuilder = new StringBuilder();
            Dump(dumpBuilder, recursive);
            return dumpBuilder.ToString();
        }

        private void Dump(StringBuilder dumpBuilder, bool recursive)
        {
            Dump(dumpBuilder, recursive, 0);
        }

        private void Dump(StringBuilder dumpBuilder, bool recursive, int indent)
        {
            string indentString = new string(' ', indent * 4);
            dumpBuilder.AppendFormat("{0}{1} ({2})", indentString, Key, Type);
            foreach (Attribute attribute in this)
            {
                dumpBuilder.AppendFormat(" | {0} ({1}): [{2}]", attribute.Key, attribute.Culture, attribute.Value);
            }

            dumpBuilder.AppendLine();

            if (recursive)
            {
                foreach (Tree child in Children)
                {
                    child.Dump(dumpBuilder, recursive, indent + 1);
                }
            }
        }

        public override string ToString()
        {
            return Dump(false);
        }

        public IEnumerator<Attribute> GetEnumerator()
        {
            LoadAttributes();
            return _Attributes.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
